package com.kusiri.kframework.salesforce.model
{
	import com.kusiri.kframework.IKfApplication;
	import com.kusiri.kframework.KfApplicationLocator;
	import com.kusiri.kframework.salesforce.util.SfUtil;
	import com.salesforce.objects.SObject;
	import com.salesforce.results.DescribeSObjectResult;
	import com.salesforce.results.Field;
	
	import flash.utils.flash_proxy;
	
	import mx.formatters.DateFormatter;
	import mx.utils.ObjectProxy;

	use namespace flash_proxy; 

	/**
	 * Represents a Salesforce object
	 * Note: Salesforce properties always start with Capital Letters
	 */
	[Bindable]
	public dynamic class SObject extends ObjectProxy implements ISObject
	{
		
		private var _hasInitialized : Boolean = false;
		private var _isInitialising : Boolean = false;
		
		public static const DEFAULT_RECORD_TYPE : String = '012000000000000AAA';
		public static const NAME_PROPERTIES:Array = ['Name','FirstName','LastName','Subject'];
		
		private var _kfApplication : IKfApplication = new KfApplicationLocator().kfApplication;
		
		private var _type:String;
		
		protected var _displayName:String;
		
		//TODO: work out why setting this as com.salesforce.objects.SObject causes a compile error
		protected var _sobject:Object;
		protected var _convertedProperties:Object;
		private var _stub:Boolean = false;

		private var _model : IKfSalesforceModel;

		public function get __model() : IKfSalesforceModel
		{
			return _model;
		}

		
		/**
		 * The description is used to convery metadata, e.g. labels, about a 
		 * value object
		 */
		public function get __description() : XML
		{
			return __model.getKfDescriptionForType(this.__type);
		}

		/**
		 * @param obj   Either the type (string) or an object
		 */
		public function __initialize(obj:*, model:IKfSalesforceModel) : void
		{
			this._model = model;
			
			if (obj==null) return;
			
			if (obj is com.salesforce.objects.SObject)
			{
				__proxiedObject = obj as com.salesforce.objects.SObject;
			}
			else if (obj is SObject)
			{
				__proxiedObject = SObject(obj).__proxiedObject;	
			}
			else
			{
				__proxiedObject = new com.salesforce.objects.SObject(obj);
			}
		}

		public function __ensureFullyLoaded(onSuccess:Function, onFail:Function) : void
		{
			if (this.Id)
			{
				_model.getSObject(this.__type, this.Id, onSuccess, onFail) ;
			}
			else
			{
				onSuccess(this);
			}
		}
		
		public function __reload(onSuccess:Function, onFail:Function) : void
		{
			_model.getSObject(this.__type, this.Id, onSuccess, onFail, true) ;
		}

		public function get __type() : String
		{
			return _type;
		}

		public function set __type(type:String) : void
		{
			throw new Error('Cannot set type directly');
		}

		public function get __stub() : Boolean
		{
			return _stub;
		}

		/**
		 * Set what actual sobject (value object) this sobject is proxying
		 */
		public function set __sourceStub(source:com.salesforce.objects.SObject) : void
		{
			_type = source.type && source.type != 'Name' ? source.type : source.Type;
			if (!_type) throw new Error('Cannot find type for stub sobject'); 
			_stub = true;
			_sobject = source;
			__displayName = __getDisplayName();
			_convertedProperties = new Object();
		}
		
		/**
		 * Return the value object
		 */
		public function get __proxiedObject() : Object
		{
			return _sobject;
		}
		
		/**
		 * Set what actual sobject (value object) this sobject is proxying
		 */
		public function set __proxiedObject(psobject:Object) : void
		{
			_type = psobject.type;
			_stub = false;
			_sobject = psobject;
			__displayName = __getDisplayName();
			_convertedProperties = new Object();
		}
		
		/**
		 * Return the name shown to a user to identify the object
		 */
		public function get __displayName() : String
		{
			return _displayName;
		}
		
		/**
		 * Set the name shown to a user to identify the object
		 */
		public function set __displayName(name:String) : void
		{
			_displayName = name;
		}
		
		/**
		 * Not implemented
		 */
		override flash_proxy function callProperty(methodName:*, ... args):* 
		{
			throw new Error('Not implemented');
		}

		public function get Id() : String
		{
			return this._sobject.Id;
		}

		/**
		 * Proxy property wrapper
		 */
	    override flash_proxy function getProperty(name:*):* 
	    {
	    	if (name is QName) name = QName(name).localName;
	    	
	    	// need to check if in fields, or if a reference
	    	// if a reference translate to an sobject and cache
	    	
	    	if (_convertedProperties.hasOwnProperty(name)) return _convertedProperties[name];
	    	
	    	var result:* = this._sobject[name];
	    	
	    	if (result is com.salesforce.objects.SObject)
	    	{
	    		// now we convert the sobject to a kusiri sobject and keep a reference in the object
	    		
	    		// is there already a sobject in the master model?		    	
		    	var sobject:ISObject = _model.getFromCache(result.Id);
		    	
		    	if ( sobject )
		    	{
		    		_convertedProperties[name] = sobject;
		    		return sobject;
		    	}
		    	
		    	// no sobject in master model, so create one and mark it as a stub
		    	
		    	// do we know the correct type?
		    	// type is normally in type or if type=Name, in Type (multi-ref)
	    		if ( result.type == 'Name' && ! result.Type )
	    		{
	    			// multi ref with no type field
	    			var fieldName:String = _model.relationshipNameToFieldName(name);
	    			var f:Field = __sfDescription.fields[fieldName];
	    			result.Type = f.referenceTo;
	    		}
		    	
		    	//TODO: move this into the model as a method?
		    	
	    		sobject = new SObject();
	    		sobject.__initialize(null, _model);
	    		sobject.__sourceStub = result;
	    		
	    		// store in the api
	    		_model.setInCache(result.Id, sobject);
	    		// and in this instance
	    		_convertedProperties[name] = sobject;
		    	
		    	return sobject;
	    	}
	    	
	    	if (result == null)
	    	{
	    		_convertedProperties[name] = null;
	    		return null;
	    	}
	    	
	    	var convertedProperty : *;
	    	var field : Field = __sfDescription.fields[name];
	    	//TODO: Need to add more types in here
	    	//if (field.type == 'boolean') return getTextInput(field);
			
			if (field.type == 'int')
			{
				convertedProperty = parseInt(result);
			}
			else if (field.type == 'date') 
			{
				convertedProperty = SfUtil.dateStringToDate(result);
			}
			else if (field.type == 'datetime')
			{
				convertedProperty = SfUtil.dateTimeStringToDate(result);
			}
			else
			{
				convertedProperty = result;
			}
	    	
	    	_convertedProperties[name] = convertedProperty;
	       
	        return convertedProperty;
	    }
	
		/**
		 * Proxy wrapper
		 */
	    override flash_proxy function setProperty(name:*, value:*):void 
	    {
	    	if (name is QName) name = QName(name).localName;
	    	
	        _sobject[name] = value;
	        if ( NAME_PROPERTIES.indexOf(name) >= 0 )
	        {
				__displayName = __getDisplayName();
	        }
	    }

		/**
		 * Computed the display name depending on fixed logic
		 */
		protected function __getDisplayName() : String
		{
			if (this.__type === 'User') return _sobject.LastName+', '+_sobject.FirstName;
			if (_sobject.Subject) return _sobject.Subject;
			return _sobject.Name;
		}		

		/**
		 * Return the field names (columns)
		 */
		public function get __fieldNames() : Array
		{
			return _model.__fieldNames[this.__type];
		}
		
		/**
		 * Gets the metadata for the object
		 */
		public function get __sfDescription() : DescribeSObjectResult
		{
			var type:String = this.__type;
			return _model.__descriptions[type];
		}
		
		public function __describeLayout(callback:Function, onFail:Function) : void
		{
			_model.describeLayout(__type, callback, onFail);
		}
		
		/**
		 * The sf record type (id)
		 * Restricted to master for now
		 */
		public function get __recordType() : String
		{
			// only master record type for now
			return DEFAULT_RECORD_TYPE;
		}
		
		/**
		 * Converts a field of an sobject into a string for display
		 * 
		 */
		public function __getPropertyAsText(property:String) : String
		{
			var field:Field = this.__sfDescription.fields[property];
			if (field.type == 'reference') 
			{
				var ref:SObject = this[field.relationshipName] as SObject;
				return ref ? ref.__displayName : '';
			}
			
			if (field.type == 'datetime' || field.type == 'date') 
			{
				return dateFormatter.format(this[property]);	
			}
			
			return this[property];
			
		}
		
		private function get dateFormatter() : DateFormatter
		{
			var dateFormatter : DateFormatter = new DateFormatter();
			dateFormatter.formatString = _kfApplication.localeSettings.dateFormat;
			return dateFormatter;
		}
		
		public function __clone() : ISObject
		{
			var obj : ISObject = new SObject();
			obj.__initialize(__proxiedObject, __model);
			return obj;
		}
		
	}
}